package com.rock.common.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

public class AESUtil {

	private final int keySize;
	private final int iterationCount;
	private final Cipher cipher;
	private static final String IV = "D27D5D9927726BDEFE7510B1BDD3D137";
	private static final String SALT = "3FF2EC019C627B945225DEBAD71A01B6985FE84C95A70EB132882F88C0A59A55";
	public static final String DEFAULT_AES_PWD = "D27D5D9927726BDEFE7510B1BDD3D137";

	private static class SingletonInstance {

		private static final AESUtil INSTANCE = new AESUtil();
	}

	public static AESUtil getInstance() {
		return SingletonInstance.INSTANCE;
	}

	public AESUtil() {
		// 128位，迭代次数
		this(128, 2);
	}

	public AESUtil(int keySize, int iterationCount) {
		this.keySize = keySize;
		this.iterationCount = iterationCount;
		try {
			this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
			throw fail(e);
		}
	}

	// 自定义盐值加密
	// passPhrase 盐，plaintext 要加密的文本
	public String encrypt(String passPhrase, String plaintext) {
		return encrypt(SALT, IV, passPhrase, plaintext);
	}

	// 默认盐值加密
	// plaintext 要加密的文本
	public String encrypt(String plaintext) {
		return encrypt(DEFAULT_AES_PWD, plaintext);
	}

	// 自定义盐值解密
	// passPhrase 盐，ciphertext 密文
	public String decrypt(String passPhrase, String ciphertext) {
		try {
			return decrypt(SALT, IV, passPhrase, ciphertext);
		} catch (IllegalStateException e) {
			return null;
		} catch (Exception e) {
		}
		return null;
	}

	public String decrypt(String plaintext) {
		return decrypt(IV, plaintext);
	}

	public String encrypt(String salt, String iv, String passphrase, String plaintext) {
		try {
			SecretKey key = generateKey(salt, passphrase);
			byte[] encrypted = doFinal(1, key, iv, plaintext.getBytes("UTF-8"));
			return hex(encrypted);
		} catch (UnsupportedEncodingException e) {
			throw fail(e);
		}
	}

	public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
		try {
			SecretKey key = generateKey(salt, passphrase);
			byte[] decrypted = doFinal(2, key, iv, hex(ciphertext));
			return new String(decrypted, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw fail(e);
		}
	}

	private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
		try {
			this.cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
			return this.cipher.doFinal(bytes);
		} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
			throw fail(e);
		}
	}

	private SecretKey generateKey(String salt, String passphrase) {
		try {
			SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
			KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), this.iterationCount, this.keySize);
			return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			throw fail(e);
		}
	}

	public static String random(int length) {
		byte[] salt = new byte[length];
		new SecureRandom().nextBytes(salt);
		return hex(salt);
	}

	public static String base64(byte[] bytes) {
		return Base64.encodeBase64String(bytes);
	}

	public static byte[] base64(String str) {
		return Base64.decodeBase64(str);
	}

	public static String hex(byte[] bytes) {
		return Hex.encodeHexString(bytes);
	}

	public static byte[] hex(String str) {
		try {
			return Hex.decodeHex(str.toCharArray());
		} catch (DecoderException e) {
			throw new IllegalStateException(e);
		}
	}

	private IllegalStateException fail(Exception e) {
		return new IllegalStateException(e);
	}
}
